export class Node<T> {
  value: T;
  next: Node<T> | null = null;
  constructor(value: T) {
    this.value = value;
  }
}

export default class LinkedList<T> {
  protected _head: Node<T> | null = null;
  protected _size: number = 0;
  constructor() {
    this._head = null;
    this._size = 0;
  }

  push(value: T) {
    const node = new Node(value);
    if (this.isEmpty()) {
      node.next = this._head;
      this._head = node;
    } else {
      let current = this._head;
      while (current && current.next !== null) {
        current = current.next;
      }
      current!.next = node;
    }
    this._size++;
  }

  insert(val: T, index: number): boolean {
    if (!this._isSafeIndex(index, true)) {
      return false;
    }
    const node = new Node(val);
    let current = this._head;
    if (index === 0) {
      node.next = current;
      this._head = node;
    } else {
      const prev = this.getNodeAt(index - 1)!;
      current = prev.next;
      prev.next = node;
      node.next = current;
    }

    this._size++;
    return true;
  }

  removeAt(index: number) {
    if (!this._isSafeIndex(index)) {
      return;
    }

    let current = this._head;
    if (index === 0) {
      this._head = current!.next;
    } else {
      const previous = this.getNodeAt(index - 1)!;
      current = previous.next!;
      previous.next = current.next;
    }
    this._size--;
  }

  remove(value: T) {
    const index = this.indexOf(value);
    if (index !== -1) {
      this.removeAt(index);
    }
  }

  getNodeAt(index: number): Node<T> | null {
    if (!this._isSafeIndex(index)) {
      return null;
    }

    let current = this._head;
    for (let i = 0; i < index && current !== null; i++) {
      current = current.next;
    }

    return current;
  }

  indexOf(value: T): number {
    let current = this._head;
    for (let i = 0; i < this.getSize(); i++) {
      if (current?.value === value) {
        return i;
      }
      current = current?.next!;
    }
    return -1;
  }

  getSize(): number {
    return this._size;
  }

  getHead(): Node<T> | null {
    return this._head;
  }

  isEmpty() {
    return this._size === 0;
  }

  private toString() {
    let str = "";
    if (this.isEmpty()) {
      return str;
    }

    let current = this._head!;
    str = `${current?.value}`;

    for (let i = 1; i < this._size && current != null; i++) {
      current = current.next!;
      str += `,${current.value}`;
    }

    return str;
  }

  protected _isSafeIndex(index: number, needLast: boolean = false): boolean {
    const lastResult = needLast ? index <= this._size : index < this._size;
    return index >= 0 && lastResult;
  }
}
